package eclant.ant.types;

import java.util.Iterator;
import java.util.List;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ResourceCollection;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;

import eclant.eclipse.jdt.ClasspathWalker;

public class ClasspathProjects extends CustomDataType implements Cloneable, ResourceCollection
{
	private final IWorkspaceRoot root;
	private IJavaProject javaProject = null;
	private List<Resource> fileSet = null;
	private boolean failOnError = true;
	private boolean exportedOnly = false;
	private boolean filePaths = true;
	private boolean includeMain = true;

	public ClasspathProjects()
	{
		this.root = ResourcesPlugin.getWorkspace().getRoot();
	}

	/**
	 * Make this instance in effect a reference to another instance.
	 * 
	 * <p>
	 * You must not set another attribute or nest elements inside this element
	 * if you make it a reference.
	 * </p>
	 * 
	 * @param r
	 *            the <code>Reference</code> to use.
	 * @throws BuildException
	 *             if there is a problem.
	 */
	@Override
	public void setRefid(Reference r) throws BuildException
	{
		if (hasAttributes())
		{
			throw tooManyAttributes();
		}
		super.setRefid(r);
	}

	public void setProject(String projectName)
	{
		gotAttribute();
		setJavaProject(root.getProject(projectName));
	}

	public void setExportedonly(boolean exportedOnly)
	{
		gotAttribute();
		this.exportedOnly = exportedOnly;
	}

	public void setFailonerror(boolean fail)
	{
		gotAttribute();
		this.failOnError = fail;
	}

	public void setUseFilePaths(boolean filePaths)
	{
		gotAttribute();
		this.filePaths = filePaths;
	}

	public void setIncludeMain(boolean includeMain)
	{
		gotAttribute();
		this.includeMain = includeMain;
	}

	@Override
	public boolean isFilesystemOnly()
	{
		return (isReference() ? getRef().isFilesystemOnly() : filePaths);
	}

	/**
	 * Fulfill the ResourceCollection contract.
	 * 
	 * @return an Iterator of Resources.
	 */
	@Override
	public synchronized Iterator<Resource> iterator()
	{
		if (isReference())
		{
			return getRef().iterator();
		}

		if (fileSet == null)
			collectFiles();

		return fileSet.iterator();
	}

	@Override
	public synchronized int size()
	{
		if (isReference())
		{
			return getRef().size();
		}

		if (fileSet == null)
			collectFiles();

		return fileSet.size();
	}

	/**
	 * Create a deep clone of this instance, except for the nested selectors
	 * (the list of selectors is a shallow clone of this instance's list).
	 * 
	 * @return a cloned Object.
	 */
	@Override
	public synchronized Object clone() throws CloneNotSupportedException
	{
		if (isReference())
		{
			return getRef().clone();
		}

		ClasspathProjects cp = (ClasspathProjects) super.clone();
		cp.javaProject = this.javaProject;
		cp.fileSet = this.fileSet;
		cp.failOnError = this.failOnError;
		return cp;
	}

	/**
	 * Perform the check for circular references and return the referenced Files
	 * collection.
	 * 
	 * @return <code>ClasspathFiles</code>.
	 */
	protected ClasspathProjects getRef()
	{
		return (ClasspathProjects) getCheckedRef();
	}

	/**
	 * Make sure we have all necessary attributes set
	 */
	private void validate() throws BuildException
	{
		if (javaProject == null)
		{
			IProject project = getDefaultProject();
			setJavaProject(project);
		}
	}

	private IProject getDefaultProject()
	{
		String buildFilePath = getProject().getProperty("ant.file");
		IPath path = Path.fromOSString(buildFilePath);
		IFile buildFile = root.getFileForLocation(path);
		return buildFile.getProject();
	}

	private void setJavaProject(IProject project) throws BuildException
	{
		try
		{
			if (!project.hasNature(JavaCore.NATURE_ID))
				throw new BuildException("Project \"" + project.getName()
						+ "\" is not Java-enabled", getLocation());

			this.javaProject = JavaCore.create(project);
		}
		catch (CoreException e)
		{
			throw new BuildException(e, getLocation());
		}
	}

	private void collectFiles() throws BuildException
	{
		try
		{
			validate();

			ProjectsClasspathVisitor result = new ProjectsClasspathVisitor(this, failOnError,
					filePaths, includeMain);
			ClasspathWalker.walkClasspath(javaProject, exportedOnly, result);
			fileSet = result.results();
		}
		catch (JavaModelException ex)
		{
			throw new BuildException(ex, getLocation());
		}
	}
}
